Analisi esplorativa del database musicale della piattaforma Spotify

Progetto per il corso di Data Science

Levratti Mattia - matr. 139022

Breve introduzione

Il mercato musicale e discografico, nella nostra epoca, appare dominato dal digital delivery e dallo streaming. Ormai, oltre alle vendite dei CD e dei vinili, recentemente ritornati in auge, contano moltissimo (e forse quasi più del resto) gli ascolti e i follower sulle piattaforme social.

In ambito musicale ne esistono davvero molte: Youtube Music, Soundcloud, Tidal, Apple Music… ma la più popolare e nota al grande pubblico resta sicuramente Spotify, forte dei suoi 356 milioni di utenti attivi (di cui 158 premium), e dei suoi oltre 40.000 brani caricati al giorno.

L’obiettivo di questo progetto è quello di rispondere a delle semplici domande che un musicista, in procinto di scrivere e pubblicare dei brani su Spotify, potrebbe porsi: quali sono i trend del momento? Come devo scrivere e ideare i miei brani per cercare di avere successo? Cosa devo fare per avere tanti ascolti, ma soprattutto cosa non devo fare?


Elenco dei contenuti

  1. Composizione del dataset e definizione delle features

  2. Analisi sulle features

  • Numero di brani in funzione dell’anno
  • Durata media dei brani, suddivisi per anno di pubblicazione
  • Distribuzione di frequenza dei valori assunti dalle features
  • Correlazione tra le features
  • Analisi musicali: chiavi e modi tonali dominanti
  • Analisi dei brani più popolari
  1. Analisi dei generi
  • Maggior popolarità
  • Chiavi e modi dominanti nei generi
  1. Analisi dei trend musicali nel tempo
  • Popolarità dei generi nel tempo
  • Andamento delle features nel tempo
  1. Potenziali sviluppi nell’immediato futuro
  • Andamento della durata
  • Andamento dei generi
  • Andamento delle features
  1. Sentiment Analysis
  • Analisi dei testi dei brani più in voga
  1. Conclusioni

Prima di cominciare, importiamo le principali librerie necessarie, nonché il file csv.

library(tidyverse)
library(dplyr)
library(tidyr)
library(ggplot2)
spotify_db <- read.csv("./tracks.csv")

Composizione del dataset e definizione delle features

Il dataset si compone di 2 documenti in formato .csv, contenenti rispettivamente

  • L’elenco di brani presenti su Spotify, con relative features
  • L’elenco dei generi, con il relativo valore medio delle features

Le features di ogni brano sono:

  1. Artists

L’elenco degli artisti della canzone.

  1. Danceability

La ballabilità descrive quanto un brano sia adatto al ballo basandosi su una combinazione di elementi musicali tra cui il tempo, la stabilità del ritmo, la forza del beat e la regolarità generale. Un valore di 0.0 è meno ballabile e 1.0 è più ballabile.

  1. Duration_ms

La durata della traccia in millisecondi.

  1. Energy

L’energia è una misura da 0.0 a 1.0 e rappresenta una misura percettiva di intensità e attività. Tipicamente, le tracce energetiche sono veloci, forti e rumorose. Per esempio, il death metal ha un’alta energia, mentre un preludio di Bach ottiene un punteggio basso. Le caratteristiche percettive che contribuiscono a questo attributo includono la gamma dinamica, l’intensità percepita, il timbro, la frequenza di inizio e l’entropia generale.

  1. Explicit

Indica se il brano include o meno del testo esplicito.

  1. Instrumentalness

Indica se un brano contiene voci. I suoni “Ooh” e “aah” sono trattati come strumentali in questo contesto. Le tracce rap ad esempio sono chiaramente “vocali”. Più il valore di instrumentalness è vicino a 1.0, maggiore è la probabilità che la traccia non contenga contenuti vocali. I valori superiori a 0,5 sono intesi come tracce strumentali, ma la fiducia è più alta quando il valore si avvicina a 1,0.

  1. Key

La chiave in cui si trova la traccia. I numeri interi mappano le altezze usando la notazione Standard Pitch Class. Per esempio 0 = C, 1 = C♯/D♭, 2 = D, e così via.

  1. Liveness

Rileva la presenza di un pubblico nella registrazione. Valori più alti di liveness rappresentano una maggiore probabilità che il brano sia stato eseguito dal vivo. Un valore superiore a 0,8 fornisce una forte probabilità che il brano sia dal vivo

  1. Loudness

Il volume complessivo di una traccia in decibel (dB). I valori di loudness sono una media di tutta la traccia e sono utili per confrontare il loudness relativo delle tracce. La loudness è la qualità di un suono che è il principale correlato psicologico della forza fisica (ampiezza). I valori tipici sono compresi tra -60 e 0 db.

  1. Mode

Il modo indica la modalità (maggiore o minore) di un brano, il tipo di scala da cui deriva il suo contenuto melodico. Maggiore è rappresentato da 1 e minore da 0.

  1. Name

Nome del brano.

  1. Popularity

La popolarità di un brano è un valore compreso tra 0 e 100, con 100 che è il più popolare. La popolarità è calcolata da un algoritmo e si basa, nella maggior parte dei casi, sul numero totale di riproduzioni che il brano ha avuto e su quanto sono recenti quelle riproduzioni. In generale, le canzoni che vengono suonate molto adesso avranno una popolarità più alta di quelle che sono state suonate molto in passato.

  1. Release_date

La data di uscita dell’album, per esempio “1981-12-15”. A seconda della precisione, potrebbe essere mostrato come “1981” o “1981-12”.

  1. Speechiness

Rileva la presenza di parole parlate in una traccia. Più la registrazione è esclusivamente parlata (ad esempio talk show, audiolibro, poesia), più il valore dell’attributo è vicino a 1.0. Valori superiori a 0,66 descrivono tracce che sono probabilmente fatte interamente di parole parlate. I valori tra 0,33 e 0,66 descrivono tracce che possono contenere sia musica che parlato, sia in sezioni che stratificate, inclusi casi come la musica rap. I valori inferiori a 0,33 rappresentano molto probabilmente musica e altre tracce non simili al parlato.

  1. Tempo

Il tempo complessivo stimato di una traccia in battiti al minuto (BPM). Nella terminologia musicale, il tempo è la velocità o il ritmo di un dato brano e deriva direttamente dalla durata media delle battute.

  1. Valence

Una misura da 0.0 a 1.0 che descrive la positività musicale trasmessa da un brano. I brani con alta valenza suonano più positivi (ad esempio, felice, allegro, euforico), mentre i brani con bassa valenza suonano più negativi (ad esempio, triste, depresso, arrabbiato).

  1. Year

Anno di uscita (estratto da release_date).

  1. Genres

Una lista dei generi usati per classificare l’album. Per esempio: “Prog Rock” , “Post-Grunge”. (Se non ancora classificato, l’array è vuoto).


Analisi sulle features

In questa sezione, cerchiamo di analizzare le varie features, il loro andamento, la loro correlazione, per verificare se emerge qualche pattern interessante.

getyear <- function(myString) {
  as.integer(unlist(strsplit(myString, "-"))[1])
}

spotify_db$release_date <- sapply(spotify_db$release_date, as.character)
spotify_db <- mutate(spotify_db, year = sapply(spotify_db$release_date, getyear))

spotify_db <- mutate(spotify_db, duration_sec = duration_ms/1000)

ggplot(data = count(spotify_db, year)) + 
  geom_col(aes(x = year, y = n)) + 
  labs(title = "Numero di brani pubblicati nel tempo", 
       subtitle = "Statico dagli anni '40, in incremento dal 2000", 
       x ="Anno di pubblicazione", 
       y = "Numero di brani") + 
  theme_minimal()

ggsave("./plots/tracksPubslished.png")

Interessante notare come ci sia un costante aumento delle pubblicazioni annuali, con il picco di pubblicazioni nel 2020. Possiamo ipotizzare che questo picco sia legato all’anno della pandemia da Covid-19?

Procediamo ora con l’analisi della durata media (in secondi) dei brani negli anni, cercando anche di osservare se c’è un trend di crescita o decrescita.

mean_dur_by_year <- aggregate(spotify_db$duration_sec, list(spotify_db$year), mean)
colnames(mean_dur_by_year) <- c("Year","Avg_track_duration")

mean_dur_by_year %>%
  ggplot(aes(x = Year, y = Avg_track_duration)) + 
  geom_point() + 
  geom_smooth() + 
  labs(title = "Durata media dei brani nel tempo", 
       subtitle = "In aumento fino agli anni '90 e poi in decrescita ", 
       x ="Anno in analisi", 
       y = "Durata media dei brani (in secondi)") + 
  theme_minimal()

ggsave("./plots/trackDuration.png")

Come si può notare, c’è un trend complessivo di aumento della durata, che però negli ultimi anni non è stato così marcato, anzi. Se ad esempio restringiamo il campo d’azione, analizzando i brani pubblicati dopo il 2005, potremmo quasi notare un trend inverso

filter(mean_dur_by_year, Year > 2005) %>%
  ggplot(aes(x = Year, y = Avg_track_duration)) + 
  geom_point() + 
  geom_smooth() + 
  labs(title = "Durata media dei brani nel tempo", 
       subtitle = "Focus sull'inversione di tendenza iniziata dal 2011", 
       x ="Anno in analisi", 
       y = "Durata media dei brani (in secondi)") + 
  theme_minimal()

ggsave("./plots/trackDurationFocus.png")

Da questo focus si nota in maniera evidente il trend di decrescita, che sembra molto marcato.

Features dei brani

Ogni brano è caratterizzato dalle features descritte in introduzione. Può risultare interessante visualizzare con un boxplot la distribuzione di questi valori. È opportuno filtrare le features, per escludere quelle non legate alla natura musicale del brano.

spotify_db_filtered <- spotify_db %>% 
  select(-year,-explicit, -artists, -id, -name, -release_date, -duration_ms, -key, -loudness, -mode, -popularity, -tempo, -duration_sec, -id_artists, -time_signature) 
  
spotify_db_filtered %>%
  gather(key = "Feature", value = "Valore") %>%
  ggplot(aes(x = reorder(Feature, Valore, FUN = median), y = Valore)) +
  geom_boxplot(outlier.colour = "gray", outlier.alpha = 0.3, outlier.size = 0.3) +
  labs(title = "Visualizazione dei valori assunti dalle varie features", 
       subtitle = "Analisi visiva mediante boxplot", 
       x = "Feature", y = "Valore") +
  theme_minimal()

ggsave("./plots/featuresBarPlot.png")

Come si può notare dal grafico, instrumentalness, speechiness e liveness sono piuttosto tendenti a valori bassi, mentre le restanti features assumono sicuramente valori più alti, e sono anche distribuite molto più uniformemente. Confrontiamo ad esempio i grafici di densità di frequenza per acousticness ed energy.

spotify_db_features <- spotify_db_filtered %>%
  gather(key = "Feature", value = "Valore")

spotify_db_features %>%
  filter(Feature == "acousticness" | Feature == "energy") %>%
  ggplot(aes(x = Valore)) +
  geom_density(aes(fill = Feature), alpha = 0.4) +
  labs(title = "Confronto distribuzione di acousticness ed energy", 
       subtitle = "Distribzione nettamente differente", 
       x = "Valore", y = "Densità") +
  theme_minimal()

ggsave("./plots/acousticnessEnergyDensity.png")

Come si può notare, energy ha una distribuzione piuttosto uniforme, leggermente spostata a sinistra, mentre acousticness presenta un due picchi agli estremi. Osserviamo anche le distribuzioni delle altre features, escludendo instrumentalness che è praticamente formata da un unico picco in basso (come si può notare dal sottostante Summary)

summary(spotify_db$instrumentalness)
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
0.0000000 0.0000000 0.0000245 0.1134508 0.0095500 1.0000000 
spotify_db_features %>%
  filter(Feature == "speechiness") %>%
  ggplot(aes(x = Valore)) +
  geom_density(aes(fill = Feature), alpha = 0.4) +
  labs(title = "Speechiness",
       x = "Valore", y = "Densità") +
  theme_minimal()

spotify_db_features %>%
  filter(Feature == "liveness") %>%
  ggplot(aes(x = Valore)) +
  geom_density(aes(fill = Feature), alpha = 0.4) +
  labs(title = "Liveness",
       x = "Valore", y = "Densità") +
  theme_minimal()

spotify_db_features %>%
  filter(Feature == "valence") %>%
  ggplot(aes(x = Valore)) +
  geom_density(aes(fill = Feature), alpha = 0.4) +
  labs(title = "Valence",
       x = "Valore", y = "Densità") +
  theme_minimal()

spotify_db_features %>%
  filter(Feature == "danceability") %>%
  ggplot(aes(x = Valore)) +
  geom_density(aes(fill = Feature), alpha = 0.4) +
  labs(title = "Danceability",
       x = "Valore", y = "Densità") +
  theme_minimal()

Correlazione tra le features

Abbiamo osservato qualche trend macroscopico, ma è interessante ora cercare di capire quanto le varie caratteristiche dei brani siano collegate. Per fare ciò viene utilizzato lo strumento statistico del coefficiente di correlazione di Pearson, che indica la dipendenza lineare che sussiste tra due variabili. Per visualizzare ciò è interessante utilizzare una Heatmap.

spotify_db_filtered <- spotify_db %>% 
  select(-year,-explicit, -artists, -id, -name, -release_date, -key, -mode, -id_artists, -duration_ms, -time_signature, -tempo)
corrMatrix <- round(cor(spotify_db_filtered, method = "pearson"), 3)

library(corrplot)
corrplot(corrMatrix, order = "hclust")

Dall’analisi della correlazione, notiamo due interessanti legami

  • Energy e Loudness, e in maniera minore Valence e Danceability sono direttamente correlate
  • Energy e Acousticness, e in maniera minore Acousticness e Loudness sono inversamente correlate

Osserviamo graficamente questi fenomeni

spotify_db %>%
  ggplot(aes(x=energy, y=loudness)) + geom_point() + geom_smooth(method = "lm") + labs(title = "Analisi di correlazione tra energia e volume dei brani", subtitle = paste("La linea di tendenza crescente testimonia la correlazione diretta, coefficiente: ", round(cor(spotify_db$energy, spotify_db$loudness, method = "pearson"), digits = 3)), x ="Energia", y = "Volume") + theme_minimal()

spotify_db %>%
  ggplot(aes(x=valence, y=danceability)) + geom_point() + geom_smooth(method = "lm") + labs(title = "Analisi di correlazione tra valenza e danzabilità e volume dei brani", subtitle = paste("Correlazione verificata, coefficiente: ", round(cor(spotify_db$valence, spotify_db$danceability, method = "pearson"), digits = 3)), x ="Valenza", y = "Danzabilità") + theme_minimal()

spotify_db %>%
  ggplot(aes(x=energy, y=acousticness)) + geom_point() + geom_smooth(method = "lm")  + labs(title = "Analisi di correlazione tra energia e acusticità dei brani", subtitle = paste("La linea di tendenza decrescente testimonia la correlazione inversa, coefficiente: ", round(cor(spotify_db$energy, spotify_db$acousticness, method = "pearson"), digits = 3)), x ="Energia", y = "Acusticità") + theme_minimal()

spotify_db %>%
  ggplot(aes(x=acousticness, y=loudness)) + geom_point() + geom_smooth(method = "lm")  + labs(title = "Analisi di correlazione tra acusticità e volume dei brani", subtitle = paste("Inversione meno marcata rispetto al caso precedente, coefficiente: ", round(cor(spotify_db$acousticness, spotify_db$loudness, method = "pearson"), digits = 3)), x = "Acusticità", y ="Volume") + theme_minimal()

Queste due analisi grafiche confermano quanto ricavato dalla heatmap precedente.

Analisi musicale: toni e modi dominanti

Dal punto di vista musicale, è anche molto interessante effettuare un’analisi macroscopica delle tonalità e dei modi prevalentemente utilizzati nel brani. In breve, la tonalità indica l’accordo dominante nel brano e il modo indica se è maggiore (felice) o minore (triste). Nel dataset le tonalità sono indicate con numeri da 0 (Do) a 11 (Si). È opportuno tradurre questi numeri in note. Analogamente, il modo è indicato con 0 per minore e 1 per maggiore.

nomi_note <- c("Do","Do#","Re","Re#","Mi","Fa","Fa#","Sol","Sol#","La","La#","Si")
tracks_by_key <- spotify_db %>%
  count(key, sort=TRUE) %>%
  mutate(chiave = nomi_note[key+1]) %>%
  select(chiave, n)

tracks_by_key %>%
  ggplot(aes(x=reorder(chiave, -n), y=n)) + geom_bar(stat="identity") + labs(title = "Confronto tra le tonalità più utilizzate", subtitle = "Vincono quelle con meno alterazioni in chiave", x = "Tonalità", y = "Numero di brani") + theme_minimal()

nomi_modi <- c("Minore","Maggiore")
tracks_by_mode <- spotify_db %>%
  count(mode, sort=TRUE) %>%
  mutate(modo = nomi_modi[mode+1]) %>%
  select(modo, n)
ggplot(tracks_by_mode, aes(x = modo, y = n)) + geom_col()  + labs(title = "Confronto tra i modi", subtitle = "Il modo maggiore è quello più utilizzato", x = "Modo", y = "Numero di brani") + theme_minimal()

Come era lecito aspettarsi, le tonalità dominanti sono Do, Sol, Re, La e Fa, e il modo è quello Maggiore. Musicalmente, queste tonalità sono le più utilizzate per via di regole musicali per la scrittura dei brani, infatti sono le tonalità con meno alterazioni (diesis e bemolli), quindi più facili da scrivere e immediate da ascoltare. Inoltre, il modo maggiore risulta ampiamente più utilizzato del minore, proprio per la sua natura gioiosa.

Il dataset ci fornisce inoltre informazioni relative alla time signature dei brani, analizziamole.

spotify_db %>%
  filter(time_signature > 1) %>%
  count(time_signature, sort = TRUE) %>%
  ggplot(aes(x = time_signature, y = n)) + geom_col()  + labs(title = "Confronto tra time signature", subtitle = "Nettamente più utilizzato il classico 4/4, ma vanno fatte delle verifiche", x = "Time Signature", y = "Numero di brani") + theme_minimal()

Ho rimosso i time signature 0/4 e 1/4, in quanto da musicista so che non esistono, ma sono dati da errori dell’algoritmo di rilevamento di Spotify (come spiegato, questa feature non è verificata ma solamente dedotta tramite algoritmo). La metrica 2/4 risulta a 0 per lo stesso motivo, probabilmente l’algoritmo la confonde con i 4/4. Allo stesso modo, è possibile affermare che molti altri tempi (specialmente composti) vengano “inglobati” dall’algoritmo in queste categorie. Questa feature insomma è la più “debole” probabilmente dell’intero dataset, e ci può quindi solamente fornire l’idea indicativa che vengono utilizzati tempi con metrica binaria o quaternaria, ma non ci da indicazioni sulla natura semplice o composta degli stessi.

Per concludere questo capitoletto, è interessante analizzare la popolarità dei brani.

most_popular_by_year = tibble(artists=NA,name=NA, year=NA, popularity=NA)
for (i in 1920:2021) {
  dt <- spotify_db %>%
    filter(year == i) %>%
    select(artists, name, year, popularity) %>%
    arrange(-popularity) %>%
    head(1)
  most_popular_by_year <- rbind(most_popular_by_year, dt)
}

most_popular_by_year %>%
  ggplot(aes(x=year, y=popularity)) + geom_col() + labs(title = "Andamento dei brani più popolari nel tempo", subtitle = "Cresce nel tempo, tuttavia i brani dagli anni '60 restano molto popolari", x = "Anno", y = "Popolarità del brano più popolare") + geom_smooth() + theme_minimal()

ggsave("./plots/popularity.png")

Tramite questo grafico si può affermare che non conviene pubblicare brani ispirati alla musica relativa agli anni ’30, ’40 e ’50, in quanto non sono attualmente affatto popolari. Chiaramente i brani più recenti sono quelli attualmente più popolari, ma anche i brani degli anni ’60 - ’90 restano molto popolari.


Analisi dei generi

Iniziamo ora l’esplorazione dei generi musicali. Il dataset mette a disposizione un ulteriore file .csv, dove vengono riportate le informazioni relative ad artisti e generi. Vediamoli, elencati in base al numero di brani associati ad ogni genere.

spotify_genres <- read.csv("./data_by_genres.csv")

spotify_genres %>%
  select(genres, popularity) %>%
  arrange(-popularity) %>%
  rename(
    Genere = genres,
    Popolarità = popularity
  ) %>%
  head(10)
               Genere Popolarità
1  chinese electropop   79.00000
2  korean mask singer   78.00000
3       dutch rap pop   77.00000
4                yaoi   77.00000
5             dong-yo   76.00000
6  rochester mn indie   76.00000
7           afroswing   75.33333
8        estonian pop   75.00000
9               j-rap   75.00000
10          irish pop   74.62500

Risultati interessanti, visto che di questi generi, almeno la metà sono completamente sconosciuti per me. In ogni caso, dai nomi possiamo notare che si tratta principalmente di generi afferenti al pop, all’indie e al rap.

Analizziamo ora, in base ai generi, tonalità e modi.

topKey_by_genres <- spotify_genres %>%
  count(key, sort=TRUE) %>%
  mutate(chiave = nomi_note[key+1]) %>%
  select(chiave, n)

topKey_by_genres %>%
  ggplot(aes(x=reorder(chiave, -n), y=n)) + geom_bar(stat="identity") + labs(title = "Confronto tra le tonalità più utilizzate", subtitle = "Chiave di sol nettamente più utilizzata", x = "Tonalità", y = "Numero di brani") + theme_minimal()

ggsave("./plots/topkeybygenres.png")
topMode_by_genres <- spotify_genres %>%
  count(mode, sort=TRUE) %>%
  mutate(modo = nomi_modi[mode+1]) %>%
  select(modo, n)

topMode_by_genres %>%
  ggplot(aes(x = modo, y = n)) + geom_col()  + labs(title = "Confronto tra i modi", subtitle = "Il modo maggiore rimane il più utilizzato", x = "Modo", y = "Numero di brani") + theme_minimal()

ggsave("./plots/topmodebygenres.png")

Per pura curiosità, siccome io sono appassionato di modo minore, verifichiamo quali sono i generi più popolari realizzati in modo minore.

spotify_genres %>%
  filter(mode == 0) %>%
  arrange(-popularity) %>%
  select(genres, popularity) %>%
  head(10)
                  genres popularity
1     chinese electropop   79.00000
2     korean mask singer   78.00000
3                   yaoi   77.00000
4              afroswing   75.33333
5                  j-rap   75.00000
6        alberta hip hop   74.50000
7  channel islands indie   74.00000
8     instrumental grime   73.13333
9             london rap   72.33333
10     japanese teen pop   71.66667

Analisi dei trend musicali nel tempo

Dopo aver osservato in maniera descrittiva le singole features, può risultare interessante andare ad osservare la loro evoluzione temporale. In primo luogo è sicuramente interessante osservare l’andamento medio delle features nel tempo.

features_by_year <- spotify_db %>%
  select(-artists, -explicit, -id, -key, -mode, -name, -release_date, -duration_ms, -tempo, -loudness, -popularity, -duration_sec, -id_artists) %>%
  filter(year > 1900) %>%
  group_by(year) %>%
  summarise(across(danceability:valence, mean, na.rm = TRUE, .names = "{col}_mean")) %>%
  pivot_longer(
    cols = danceability_mean:valence_mean,
    names_to = "feature_full",
    values_to = "Value"
  ) %>%
  separate(feature_full, into = c("Feature","applied_function"), sep = "_")

colors <- c(acousticness_mean = "red",
            danceability_mean = "green",
            energy_mean = "blue",
            instrumentalness_mean = "yellow",
            liveness_mean = "pink",
            speechiness_mean = "orange",
            valence_mean = "gray"
              )

features_by_year %>%
  ggplot(aes(x=year, y=Value, group=Feature, color = Feature)) +
  geom_line() + 
  scale_color_discrete(name = "Legenda") + 
  labs(title = "Andamento delle features nel tempo", subtitle = "Interessante andamento di Acousticness ed Energy", x = "Anno", y = "Valore") + theme_minimal()

ggsave("./plots/featuresovertime.png")

Fino agli anni ’50 c’è un andamento irregolare di tutti i dati, probabilmente dato dall’incompletezza del dataset per dati così antiquati. Interessante notare l’andamento di Energy e Acousticness: il primo aumenta nettamente nel tempo, mentre il secondo al contrario ha un brusco calo. Isoliamoli ed analizziamoli meglio.

features_by_year %>%
  filter(year >= 1950 & (Feature == "acousticness" | Feature == "energy")) %>%
  ggplot(aes(x=year, y=Value, group=Feature, color = Feature)) +
  geom_line() + 
  geom_smooth() +
  scale_color_discrete(name = "Legenda") + 
  labs(title = "Focus su Acousticness ed Energy", subtitle = "Interessante andamento di Acousticness ed Energy", x = "Anno", y = "Valore") + theme_minimal()

Come si può notare, le due features hanno un valore medio molto similare poco prima del 1970. Per il resto, globalmente il trend resta decrescente per Acousticness e crescente per Energy, seppure i valori sembrino recentemente mediamente piatti. Tutto ciò è un’ulteriore conferma di quanto espresso in precedenza su queste due features, tramite correlazione di Pearson.


Potenziali sviluppi futuri

Effettuare questa analisi ci ha permesso di comprendere meglio l’attuale panorama musicale presente su Spotify, e già con queste informazioni, un nostro ipotetico musicista in erba avrebbe molte informazioni su cui meditare, relativamente al modo in cui strutturare le sue uscite musicali. Ora però sarebbe interessante spingersi anche un po’ oltre e provare, sulla base dei dati raccolti, ad effettuare delle previsioni. Per fare ciò, ho scelto di impostare ed utilizzare un modello di regressione polinomiale, applicandolo alle varie features.

modelAcousticness <- lm(acousticness ~ poly(year, 2, raw = TRUE), data = spotify_db)
p1 <- predict(modelAcousticness, data.frame(year=c(2022:2027)))

modelEnergy <- lm(energy ~ poly(year, 2, raw = TRUE), data = spotify_db)
p2 <- predict(modelEnergy, data.frame(year=c(2022:2027)))

modelDanceability <- lm(danceability ~ poly(year, 2, raw = TRUE), data = spotify_db)
p3 <- predict(modelDanceability, data.frame(year=c(2022:2027)))

modelInstrumentalness <- lm(instrumentalness ~ poly(year, 2, raw = TRUE), data = spotify_db)
p4 <- predict(modelInstrumentalness, data.frame(year=c(2022:2027)))

modelLiveness <- lm(liveness ~ poly(year, 2, raw = TRUE), data = spotify_db)
p5 <- predict(modelLiveness, data.frame(year=c(2022:2027)))

modelSpeechiness <- lm(speechiness ~ poly(year, 2, raw = TRUE), data = spotify_db)
p6 <- predict(modelSpeechiness, data.frame(year=c(2022:2027)))

modelValence <- lm(valence ~ poly(year, 2, raw = TRUE), data = spotify_db)
p7 <- predict(modelValence, data.frame(year=c(2022:2027)))

Valori <- format(c(p1,p2,p3,p4,p5,p6,p7), scientific = FALSE)
Anno <- c(rep(c(2022,2023,2024,2025,2026,2027),7))
Feature <- c(rep("Acousticness",6),rep("Energy",6),rep("Danceability",6),rep("Instrumentalness",6),rep("Liveness",6),rep("Speechiness",6),rep("Valence",6))

test <- data.frame(Feature, Anno, Valori)
test$Valori <- sapply(test$Valori, as.numeric)
test$Valori = test$Valori/100

test %>%
  filter(Anno == 2022) %>%
  select(Feature, Valori)
           Feature Valori
1     Acousticness   0.24
2           Energy   0.37
3     Danceability   0.31
4 Instrumentalness   0.01
5         Liveness   0.18
6      Speechiness   0.05
7          Valence   0.30
ggplot(test, aes(x = Anno, y = Valori, color = Feature)) +
  geom_line() +
  geom_point() +
  labs(title = "Previsione quinquennale", subtitle = "Valori delle features", x = "Anno", y = "Valore previsto") + theme_minimal()

ggsave("./plots/futurefeatures.png")

Da questa analisi, possiamo vedere quali sono i valori medi previsti per le varie features per il prossimo anno, e anche il loro andameno quinquennale previsto. Indicativamente, si prospettano brani piuttosto energici e ballabili, poco strumentali e acustici, non registrati in live, con un mood lievemente tendente verso il triste o greve.


Sentiment Analysis

In conclusione, può risultare molto interessante analizzare i testi dei brani più popolari, per osservare l’evoluzione (se c’è) dei sentimenti dei testi più popolari nel tempo.

L’idea è di costruire un dataframe apposito, con i due brani più popolari di ogni anno. Per ogni riga, quindi per ogni brano, è necessario ottenere il testo, su cui in seguito effettuare la sentiment analysis.

sentimentAnalysisDF = tibble(artists=NA,name=NA, year=NA, popularity=NA)
for (i in 1960:2021) {
  dt <- spotify_db %>%
    filter(year == i) %>%
    select(artists, name, year, popularity) %>%
    arrange(-popularity) %>%
    head(1)
  sentimentAnalysisDF <- rbind(sentimentAnalysisDF, dt)
}

sentimentAnalysisDF$artists <- sapply(sentimentAnalysisDF$artists, as.character)
sentimentAnalysisDF$artists <- str_sub(sentimentAnalysisDF$artists, 2, -2)
sentimentAnalysisDF <- drop_na(sentimentAnalysisDF)
sentimentAnalysisDF = sentimentAnalysisDF %>%
  separate(artists, "Artista", sep = ",")
sentimentAnalysisDF$Artista <- str_sub(sentimentAnalysisDF$Artista, 2, -2)
sentimentAnalysisDF[, "text"] <- ""

sentimentAnalysisDF = sentimentAnalysisDF %>%
  mutate(queryAdd = paste("http://api.chartlyrics.com/apiv1.asmx/SearchLyricDirect?artist=", Artista, "&song=", name, sep =  "")) %>%
  mutate(queryAdd = str_replace_all(queryAdd, " ", "%20"))

library(XML)
for (song in 1:124) {
  tryCatch ( {
    xmlRawData = xmlToList(sentimentAnalysisDF$queryAdd[song], addAttributes = TRUE, simplify = FALSE)
    testo = xmlRawData$Lyric
    sentimentAnalysisDF$text[song] = testo
  },
  error = function(err) {
    cat("Errore, testo mancante")
  }
  )
}
failed to load HTTP resource
Errore, testo mancantefailed to load HTTP resource
Errore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancanteErrore, testo mancante

Utilizzando la API ChartLyrics è stato possibile ottenere molti testi in maniera automatizzata. Purtroppo, essendo una soluzione gratuita non include tutti i testi. I mancanti li inserisco a mano.

library(tidytext)
sentimentFinalTable <- sentimentAnalysisDF %>%
  mutate(songId = row_number()) %>%
  ungroup() %>%
  unnest_tokens(word, text) %>%
  inner_join(get_sentiments("bing")) %>%
  count(name, anno = year, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative) %>%
  arrange(anno)

summary(sentimentFinalTable$sentiment)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-32.000  -4.000   0.500   1.758   8.000  36.000 
sentimentFinalTable %>% 
  ggplot(aes(anno,sentiment, fill=name)) +
  geom_col(show.legend = FALSE) +
  labs(title = "Andamento del Sentiment", subtitle = "Canzone più popolare per ciascun anno", x = "Anno", y = "Valore di sentiment") + theme_minimal()

ggsave("./plots/sentiment.png")

Questa analisi purtroppo non ha restituito nessun pattern interessante; sentimenti positivi e negativi si alternano senza trend o valori predominanti. L’unica cosa che si può ricavare è che appunto, il sentiment del testo dei brani non ha una grande rilevanza ai fini della popolarità.


Conclusioni

Al termine di questo progetto, è possibile ricavare alcune interessanti conclusioni che possono aiutarci a rispondere alle domande in oggetto: quali sono i parametri musicali maggiormente utilizzati su Spotify? In che modo un musicista emergente può essere relativamente sicuro di pubblicare brani che verranno apprezzati dalla massa?

Innanzitutto, è doveroso ricordare che siamo nel campo delle ipotesi: questa analisi ci può soltanto aiutare, ci fa avere una panoramica di tutto ciò. Ma stiamo parlando di musica, emozioni umane, e soprattutto di un’analisi non professionale, basata su dati circoscritti territorialmente.

Detto ciò, traiamo le conclusioni:

  • La durata media dei brani si attesta intorno ai 200 secondi, quindi circa 3 minuti e 20 secondi, con un trend di decrescita: è opportuno quindi pensare di pubblicare brani leggermente più corti

  • Strumentalità, parlato e liveness si attestano verso valori bassi, in trend decrescente: è opportuno quindi pensare di pubblicare brani non prevalentemente strumentali o parlati, e non registrati in live

  • Energia, valenza e danzabilità si attestano su valori medio/alti, in trend crescente/stabile: via libera a brani energici, positivi e movimentati

  • Le tonalità più utilizzate sono quelle con meno alterazioni in chiave, quindi è opportuno pensare di utilizzare il classico circolo delle quinte (do - sol - re - la - fa)

  • Le misure più utilizzate sono quelle a multipli di due (2/4, 4/4), quindi meglio evitare misure ternarie o dispari

  • La popolarità dei brani antecedenti agli anni ’60 risulta in discesa, quindi meglio ispirarsi solo a brani pubblicati successivamente

  • In merito al testo non sono stati trovati pattern da seguire, quindi libero sfogo ad emozioni sia positive che negative